package validator

import (
	"errors"
	"fmt"
	"net"
	"net/url"
	"regexp"
	"strings"
	"unicode"

	"golang.org/x/net/idna"
	"golang.org/x/net/publicsuffix"
)

var (
	// 域名标签正则：
	// - 不允许下划线（用于可注册域部分的标签，以及前缀标签除首字符外的部分）
	labelRegexNoUnderscore = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`)

	// 常见的多级TLD
	multiLevelTLDs = map[string]bool{
		"co.uk": true, "co.jp": true, "co.kr": true, "co.nz": true,
		"com.au": true, "com.br": true, "com.cn": true, "com.mx": true,
		"net.au": true, "net.br": true, "net.cn": true,
		"org.au": true, "org.br": true, "org.cn": true, "org.uk": true,
		"ac.uk": true, "gov.uk": true, "gov.cn": true,
	}
)

// ValidateDomain 验证域名格式
func ValidateDomain(domain string) error {
	if domain == "" {
		return errors.New("域名不能为空")
	}

	// 去除尾部的点（DNS 规范允许）
	domain = strings.TrimSuffix(domain, ".")

	// 处理通配符前缀（*.）
	hasWildcard := strings.HasPrefix(domain, "*.")
	if hasWildcard {
		domain = strings.TrimPrefix(domain, "*.")
		if domain == "" {
			return errors.New("域名格式无效：通配符后需要域名")
		}
	}

	// 检查是否为 IP 地址（IPv4 或 IPv6）
	if net.ParseIP(domain) != nil {
		return errors.New("不支持 IP 地址，请使用域名")
	}

	// 检查是否包含无效字符
	if strings.ContainsAny(domain, " @#$%^&*()+=[]{}\\|;:'\"<>,?/") {
		return errors.New("域名格式无效：包含非法字符")
	}

	// 检查是否以点开始或包含连续的点
	if strings.HasPrefix(domain, ".") || strings.Contains(domain, "..") {
		return errors.New("域名格式无效：点号位置不正确")
	}

	// 将域名转换为 ASCII（处理 IDN）
	asciiDomain, err := idna.ToASCII(domain)
	if err != nil {
		return fmt.Errorf("域名格式无效：%v", err)
	}

	// 分割域名为标签
	labels := strings.Split(asciiDomain, ".")
	if len(labels) < 2 {
		return errors.New("域名格式无效：至少需要两个标签")
	}

	// 计算可注册域长度，用于限定下划线仅可出现在前缀部分
	e, _ := publicsuffix.EffectiveTLDPlusOne(asciiDomain)
	etldLabels := 2
	if e != "" {
		etldLabels = len(strings.Split(e, "."))
	}
	prefixLen := len(labels) - etldLabels
	if prefixLen < 0 {
		prefixLen = 0
	}

	// 验证每个标签（前缀标签可选首字符下划线；其余位置不允许下划线；可注册域部分不允许下划线）
	for i, label := range labels {
		if len(label) == 0 {
			return errors.New("域名格式无效：标签不能为空")
		}
		if len(label) > 63 {
			return errors.New("域名标签过长：最多 63 个字符")
		}
		if strings.HasPrefix(label, "-") || strings.HasSuffix(label, "-") {
			return errors.New("域名格式无效：标签不能以连字符开头或结尾")
		}
		// 非 punycode 标签应用正则
		if !strings.HasPrefix(label, "xn--") {
			if i < prefixLen {
				// 前缀标签：允许下划线仅在首字符
				if strings.Contains(label, "_") {
					if !strings.HasPrefix(label, "_") {
						return errors.New("域名格式无效：下划线只允许在前缀标签的首位")
					}
					// 首字符是下划线时，其余部分必须满足无下划线的规则
					rest := label[1:]
					if !labelRegexNoUnderscore.MatchString(rest) {
						return errors.New("域名格式无效：下划线后的前缀标签格式错误")
					}
				} else {
					if !labelRegexNoUnderscore.MatchString(label) {
						return errors.New("域名格式无效：前缀标签格式错误")
					}
				}
			} else {
				if strings.Contains(label, "_") {
					return errors.New("域名格式无效：可注册域标签中不允许下划线")
				}
				if !labelRegexNoUnderscore.MatchString(label) {
					return errors.New("域名格式无效：可注册域标签格式错误")
				}
			}
		}
	}

	return nil
}

// NormalizeDomain 标准化域名
func NormalizeDomain(domain string) string {
	// 去除空格
	domain = strings.TrimSpace(domain)

	// 转换为小写
	domain = strings.ToLower(domain)

	// 去除尾部的点
	domain = strings.TrimSuffix(domain, ".")

	// 尝试将 IDN 转换为 punycode
	if IsIDN(domain) {
		if asciiDomain, err := idna.ToASCII(domain); err == nil {
			return asciiDomain
		}
	}

	return domain
}

// IsIDN 检查是否为国际化域名
func IsIDN(domain string) bool {
	for _, r := range domain {
		if r > unicode.MaxASCII {
			return true
		}
	}
	return false
}

// ExtractRootDomain 提取根域名（使用 publicsuffix 库以提高准确性）
func ExtractRootDomain(domain string) (string, error) {
	// 先验证域名
	if err := ValidateDomain(domain); err != nil {
		return "", err
	}

	// 标准化域名
	domain = NormalizeDomain(domain)

	// 使用 publicsuffix 库获取 eTLD+1
	eTLDPlusOne, err := publicsuffix.EffectiveTLDPlusOne(domain)
	if err != nil {
		// 如果 publicsuffix 失败，回退到简单的逻辑
		parts := strings.Split(domain, ".")
		if len(parts) < 2 {
			return domain, nil
		}

		// 检查是否为多级 TLD
		if len(parts) >= 3 {
			// 检查最后两部分是否为已知的多级 TLD
			possibleTLD := strings.Join(parts[len(parts)-2:], ".")
			if multiLevelTLDs[possibleTLD] {
				if len(parts) >= 3 {
					return strings.Join(parts[len(parts)-3:], "."), nil
				}
				return domain, nil
			}
		}

		// 默认返回最后两部分
		return strings.Join(parts[len(parts)-2:], "."), nil
	}

	return eTLDPlusOne, nil
}

// IsSubdomain 检查是否为子域名
func IsSubdomain(domain string) (bool, error) {
	root, err := ExtractRootDomain(domain)
	if err != nil {
		return false, err
	}

	normalized := NormalizeDomain(domain)
	return normalized != root, nil
}

// CleanDomainInput 对原始域名输入做宽松兼容处理：
// - 去除所有空白字符与控制字符
// - 将中文标点转换：中文逗号 → 英文逗号，中文句号/全角点 → 英文点
// - 从 URL 中提取主机名（支持 http(s)://、//、含路径的域名）
// - 处理可能的邮箱形式（取 @ 之后的部分）
// - 去除端口
// - 仅保留合法字符：字母/数字/连字符/点/下划线/IDN（Unicode）；保留开头的通配符"*."，移除其他位置的*
// - 标准化小写，移除首尾点
func CleanDomainInput(raw string) string {
	if raw == "" {
		return ""
	}

	// 1) 去除所有空白与控制字符
	b := strings.Builder{}
	for _, r := range raw {
		if unicode.IsSpace(r) || r == '\u0000' || r == '\uFEFF' {
			continue
		}
		b.WriteRune(r)
	}
	s := b.String()

	// 2) 中文标点转英文
	s = strings.ReplaceAll(s, "，", ",")
	s = strings.ReplaceAll(s, "。", ".") // U+3002
	s = strings.ReplaceAll(s, "．", ".") // U+FF0E
	s = strings.ReplaceAll(s, "｡", ".") // U+FF61

	s = strings.TrimSpace(s)

	// 3) 从 URL 提取主机（尽量解析）
	// 支持 http(s)://、//host、host/path
	if strings.Contains(s, "://") || strings.HasPrefix(s, "//") || strings.Contains(s, "/") {
		parsed, err := url.Parse(s)
		if err == nil {
			host := parsed.Host
			if host == "" {
				// 可能是 host/path 这种；尝试补协议再次解析
				if p2, err2 := url.Parse("http://" + s); err2 == nil {
					host = p2.Host
				}
				if host == "" {
					// 兜底：取第一个斜杠之前
					if idx := strings.IndexByte(s, '/'); idx > 0 {
						host = s[:idx]
					}
				}
			}
			if host != "" {
				s = host
			}
		} else {
			// 兜底：取第一个斜杠之前
			if idx := strings.IndexByte(s, '/'); idx > 0 {
				s = s[:idx]
			}
		}
	}

	// 4) 处理邮箱形式：取 @ 之后
	if at := strings.LastIndexByte(s, '@'); at >= 0 && at < len(s)-1 {
		s = s[at+1:]
	}

	// 5) 去除端口
	if h, _, err := net.SplitHostPort(s); err == nil && h != "" {
		s = h
	} else {
		// 如果不是 host:port 形式，但包含单个冒号（且不是 IPv6），也尝试去掉
		if strings.Count(s, ":") == 1 && !strings.Contains(s, "]") && !strings.Contains(s, "[") {
			parts := strings.SplitN(s, ":", 2)
			if len(parts) == 2 && parts[0] != "" {
				s = parts[0]
			}
		}
	}

	// 6) 允许开头通配符"*."，移除其他位置的"*"
	keepWildcard := strings.HasPrefix(s, "*.")
	if keepWildcard {
		s = strings.TrimPrefix(s, "*.")
	}

	// 7) 过滤无效字符（保留：字母/数字/连字符/点/下划线/Unicode IDN）
	filtered := strings.Builder{}
	for _, r := range s {
		if r == '.' || r == '-' || r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) || r > unicode.MaxASCII {
			filtered.WriteRune(r)
		}
		// 其他字符全部忽略
	}
	s = filtered.String()

	// 8) 还原通配符前缀
	if keepWildcard && s != "" {
		s = "*." + s
	}

	// 9) 标准化：小写、移除首尾点
	s = strings.ToLower(strings.Trim(s, "."))

	// 10) 约束下划线仅出现在前缀标签：移除可注册域部分所有下划线
	//     （如 _acme-challenge.example.com 保留前缀标签的下划线，example.com 不允许下划线）
	base := s
	if keepWildcard && strings.HasPrefix(base, "*.") {
		base = strings.TrimPrefix(base, "*.")
	}
	asciiBase, err := idna.ToASCII(base)
	if err == nil && asciiBase != "" {
		labels := strings.Split(base, ".")
		e, _ := publicsuffix.EffectiveTLDPlusOne(asciiBase)
		etldLabels := 2
		if e != "" {
			etldLabels = len(strings.Split(e, "."))
		}
		prefixLen := len(labels) - etldLabels
		if prefixLen < 0 {
			prefixLen = 0
		}
		for i := range labels {
			if i >= prefixLen {
				labels[i] = strings.ReplaceAll(labels[i], "_", "")
			}
		}
		cleaned := strings.Join(labels, ".")
		if keepWildcard {
			cleaned = "*." + cleaned
		}
		s = cleaned
	}

	return s
}

// ToUnicode 将 punycode 域名转换为 Unicode，用于前端友好展示
func ToUnicode(domain string) string {
	if domain == "" {
		return ""
	}
	if u, err := idna.ToUnicode(domain); err == nil && u != "" {
		return u
	}
	return domain
}
